#!/usr/bin/python
# Copyright (c) 2012 The Native Client Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

"""Recipes for NativeClient toolchain packages.

The real entry plumbing is in toolchain_main.py.
"""


#
#  Include order:
#     clang: stdint.h
#       include/stdint.h
#       z

import argparse
import fnmatch
import os
import stat
import shutil
import StringIO
import sys

BUILD_SCRIPT = os.path.abspath(__file__)
BIONIC_SRC = os.path.dirname(BUILD_SCRIPT)
TOOLCHAIN_BUILD = os.path.dirname(os.path.dirname(os.path.dirname(BIONIC_SRC)))
NATIVE_CLIENT = os.path.dirname(TOOLCHAIN_BUILD)

PKG_VERSION = os.path.join(NATIVE_CLIENT, 'build', 'package_version')
sys.path.append(PKG_VERSION)

DEFAULT_OUTPUT_ROOT =  os.path.join(TOOLCHAIN_BUILD, 'out-bionic')


sys.path.append(TOOLCHAIN_BUILD)
import process
from file_update import Mkdir, Rmdir, Symlink
from file_update import NeedsUpdate, UpdateFromTo, UpdateText


sys.path.append(NATIVE_CLIENT)
import pynacl.gsd_storage
import pynacl.hashing_tools
import pynacl.log_tools
import pynacl.platform
import pynacl.repo_tools


CPU_TYPES = ['x86_64']
ARCH_TYPES = ['i686', 'x86_64']


def ReplaceText(text, maplist):
  for m in maplist:
    for key in m:
      text = text.replace(key, m[key])
  return text


def ReplaceArch(text, arch, subarch=None):
  ARCH_MAP = {
    'arm': 'arm',
    'i686': 'i686',
    'x86_64': 'amd64'
  }
  CPU_MAP = {
    'arm': 'arm',
    'i686': 'x86',
    'x86_64': 'x86',
  }
  GCC_MAP = {
    'arm': 'arm',
    'i686': 'i686',
    'x86_64': 'x86_64'
  }
  INC_MAP = {
    'arm' : 'arm-nacl/include',
    'i686': 'x86_64-nacl/include',
    'x86_64': 'x86_64-nacl/include',
  }
  LIB_MAP = {
    'arm' : 'arm-nacl/lib',
    'i686': 'x86_64-nacl/lib32',
    'x86_64': 'x86_64-nacl/lib',
  }
  MATH_MAP = {
    'arm': 'arm',
    'i686': 'i386',
    'x86_64': 'amd64'
  }
  NACL_MAP = {
    'arm': 'arm-nacl',
    'i686': 'x86_64-nacl',
    'x86_64': 'x86_64-nacl',
  }

  REPLACE_MAP = {
    '$ARCH': ARCH_MAP[arch],
    '$CPU': CPU_MAP[arch],
    '$GCC': GCC_MAP[arch],
    '$INC': INC_MAP[arch],
    '$LIB': LIB_MAP[arch],
    '$MATH': MATH_MAP[arch],
    '$NACL': NACL_MAP[arch],
  }
  return ReplaceText(text, [REPLACE_MAP])


def BuildPNaCl(options):
  tc_args = ['python', 'toolchain_build_pnacl.py', '-o', options.output,
             '--install', os.path.join(options.output, 'bionic_work')]
  if options.no_cache:
    tc_args += ['--no-use-cached-results', '--no-use-remote-cache']
  if options.sync:
    tc_args += ['-y']
  process.Run(tc_args, outfile=sys.stdout)


def UpdatePNaClBuildObjects(options):
  COPY_FULL = [
    'arm_bc-nacl', 'bin', 'docs',
    'i686_bc-nacl', 'include', 'le32-nacl',
    'share', 'target_lib_compiler',
    'translator', 'x86_64_bc-nacl',
    'FEATURE_VERSION', 'README', 'REV',
    'lib',
    'arm-nacl/bin', 
    'x86_64-nacl/bin'
  ]
  BOGUS = [
    'lib/clang/3.4/include',
    'lib/clang/3.4/lib/arm_bc-nacl',
    'lib/clang/3.4/lib/i686_bc-nacl',
    'lib/clang/3.4/lib/le32-nacl',
    'lib/clang/3.4/lib/x86_64-nacl',
  ]
  srcpath = os.path.join(options.output, 'bionic_work')
  dstpath = os.path.join(options.output, 'bionic_install')
  for dirpath in COPY_FULL:
    UpdateFromTo(os.path.join(srcpath, dirpath), os.path.join(dstpath, dirpath))

  EMPTY = """/*
 * This is a dummy linker script.
 * libnacl.a, libcrt_common.a, crt0.o crt1.o crti.o and crtn.o are all
 * empty.  Instead all the work is done by crtbegin(S).o and crtend(S).o and
 * the bionic libc.  These are provided for compatability with the newlib
 * toolchain binaries.
 */"""
  EMPTY_FILES = ['crt0.o', 'crt1.o', 'crti.o', 'crtn.o',
                 'libnacl.a', 'libcrt_common.a', 'libpthread.a']

  for arch in ARCH_TYPES:
    # Create empty objects and libraries
    libpath = ReplaceArch(os.path.join(dstpath, '$LIB'), arch)
    for name in EMPTY_FILES:
      UpdateText(os.path.join(libpath, name), EMPTY)


def UpdateBionicHeaders(options):
  BIONIC_PAIRS = [
    ('libc/arch-nacl/syscalls/irt_poll.h', '$INC/irt_poll.h'),
    ('libc/arch-nacl/syscalls/irt_socket.h', '$INC/irt_socket.h'),
    ('libc/include', '$INC'),
    ('libc/arch-nacl/syscalls/nacl_socket.h', '$INC/nacl_socket.h'),
    ('libc/arch-nacl/syscalls/nacl_stat.h', '$INC/nacl_stat.h'),
    ('libc/arch-pnacl/include/machine',
        '$INC/machine'),
    ('libc/kernel/common', '$INC'),
    ('libc/kernel/arch-$CPU/asm', '$INC/asm'),
    # Math Includes
    ('libm/include', '$INC'),
    ('libm/$MATH', '$INC'),
    # Misc
    ('safe-iop/include', '$INC'),
    ('nacl/$INC', '$INC'),
  ]

  for arch in CPU_TYPES:
    incpath = os.path.join(options.output, 'bionic_install')
    incpath = ReplaceArch(incpath, arch)

    Mkdir(incpath)

    if options.header_clobber:
      dst = ReplaceArch(os.path.join(incpath, '$INC'), arch)
      Rmdir(dst)
      print "Clobbered: " + dst
      sys.exit(1)

    for src, dst in BIONIC_PAIRS:
      srcpath = ReplaceArch(os.path.join(BIONIC_SRC, src), arch)
      dstpath = ReplaceArch(os.path.join(incpath, dst), arch)
      UpdateFromTo(srcpath, dstpath)


def MungeIRT(src, dst):
  replace_map = {
    'off_t': 'int64_t',
    'native_client/src/untrusted/irt/' : '',
  }

  with open(src, 'r') as srcf:
    text = srcf.read()
    text = ReplaceText(text, [replace_map])
    with open(dst, 'w') as dstf:
      dstf.write(text)


def UpdateBionicIRT(options):
  for arch in CPU_TYPES:
    for name in ['irt.h', 'irt_dev.h']:
      src = os.path.join(NATIVE_CLIENT, 'src', 'untrusted', 'irt', name)
      dst = os.path.join(options.output, 'bionic_install', '$INC')
      dst = ReplaceArch(dst, arch)
      Mkdir(dst)
      MungeIRT(src, os.path.join(dst, name))


def GetProjectPaths(out_dir, arch, project):
  srcpath = os.path.join(BIONIC_SRC, project)
  workpath = os.path.join(out_dir, 'bionic_$GCC_work')
  toolpath = os.path.join(out_dir, 'bionic_install')
  clangpath = os.path.join(toolpath, 'lib', 'clang', '3.4', 'lib')

  workpath = ReplaceArch(os.path.join(workpath, project), arch)
  instpath = ReplaceArch(os.path.join(toolpath, '$LIB'), arch)
  gccpath = ReplaceArch(os.path.join(clangpath, '$GCC-nacl'), arch)
  out = {
    'bionic': BIONIC_SRC,
    'src': srcpath,
    'work': workpath,
    'ins': instpath,
    'gcc': gccpath,
    'tc': toolpath,
  }
  return out


def CreateProject(options, arch, project, clobber=False):
  paths = GetProjectPaths(options.output, arch, project)

  MAKEFILE_TEMPLATE = """
# Copyright (c) 2014 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

# GNU Makefile based on shared rules provided by the Native Client SDK.
# See README.Makefiles for more details.

NATIVE_CLIENT_PATH?=$(nacl_path)
TOOLCHAIN_PATH?=$(tc_path)
TOOLCHAIN_PREFIX:=$(TOOLCHAIN_PATH)/bin/$NACL-

CC:=$(TOOLCHAIN_PREFIX)clang
CXX:=$(TOOLCHAIN_PREFIX)clang++
AR:=$(TOOLCHAIN_PREFIX)ar
LD:=$(TOOLCHAIN_PREFIX)clang++

SRC_ROOT=$(src_path)
DST_ROOT=$(dst_path)
INS_ROOT=$(ins_path)
GCC_ROOT=$(gcc_path)

NACL_ARCH=$NACL
GCC_ARCH=$GCC

MAKEFILE_DEPS:=$(bionic_path)/tc_bionic.mk
MAKEFILE_DEPS+=$(src_path)/Makefile

include $(bionic_path)/tc_bionic.mk
include $(src_path)/Makefile
"""
  remap = {
    '$(src_path)': paths['src'],
    '$(dst_path)': paths['work'],
    '$(gcc_path)': paths['gcc'],
    '$(ins_path)': paths['ins'],
    '$(tc_path)':  paths['tc'],
    '$(bionic_path)': paths['bionic'],
    '$(build_tc_path)': TOOLCHAIN_BUILD,
    '$(nacl_path)': NATIVE_CLIENT,
  }
  text = ReplaceText(MAKEFILE_TEMPLATE, [remap])
  text = ReplaceArch(text, arch)

  if options.clobber:
    print 'Clobbering Bionic project directory: ' + paths['work']
    Rmdir(paths['work'])

  Mkdir(paths['work'])
  Mkdir(paths['ins'])
  UpdateText(os.path.join(paths['work'], 'Makefile'), text)


def ConfigureBionicProjects(options):
  PROJECTS = ['libc', 'newtests']
  for arch in ['i686', 'x86_64']:
    for project in PROJECTS:
      print 'Configure %s for %s.' % (project, arch)
      CreateProject(options, arch, project)


def MakeBionicProject(options, project, targets=None, clobber=False):
  for arch in ARCH_TYPES:
    paths = GetProjectPaths(options.output, arch, project)
    workpath = paths['work']
    targets = targets or []
    targetlist = ' '.join(targets)
    print 'Building %s for %s at %s %s.' % (project, arch, workpath, targetlist)
    if clobber:
      args = ['make', '-j12', 'V=1', 'clean']
      if process.Run(args, cwd=workpath, outfile=sys.stdout):
        raise RuntimeError('Failed to clean %s for %s.\n' % (project, arch))

    args = ['make', '-j12', 'V=1'] + targets
    if process.Run(args, cwd=workpath, outfile=sys.stdout):
      print "Building at: " + workpath
      args = ['make', '-j1', 'V=1'] + targets
      process.Run(args, cwd=workpath, outfile=sys.stdout)
      raise RuntimeError('Failed to build %s for %s.\n' % (project, arch))
    print 'Done with %s for %s.\n' % (project, arch)


def main(argv):
  parser = argparse.ArgumentParser(add_help=False)
  parser.add_argument(
      '-v', '--verbose', dest='verbose',
      default=False, action='store_true',
      help='Produce more output.')

  parser.add_argument(
      '-c', '--clobber', dest='clobber',
      default=False, action='store_true',
      help='Clobber working directories before building.')
  
  parser.add_argument(
      '-h', '--header-clobber', dest='header_clobber',
      default=False, action='store_true',
      help='Clobber headers directories before building.')

  parser.add_argument(
      '--no-cache', dest='no_cache',
      default=False, action='store_true',
      help='Do not use cached results.')

  parser.add_argument(
      '-f', '--skip-pnacl-build', dest='fast_build',
      default=False, action='store_true',
      help='Skip rebuilding building pnacl.')

  parser.add_argument(
      '-s', '--sync', dest='sync',
      default=False, action='store_true',
      help='Sync sources first.')

  parser.add_argument(
      '-b', '--buildbot', dest='buildbot',
      default=False, action='store_true',
      help='Running on the buildbot.')

  parser.add_argument(
      '-l', '--llvm', dest='llvm',
      default=False, action='store_true',
      help='Enable building via llvm.')

  parser.add_argument(
      '-u', '--upload', dest='upload',
      default=False, action='store_true',
      help='Upload build artifacts.')

  parser.add_argument(
      '-o', '--output', dest='output',
      default=DEFAULT_OUTPUT_ROOT,
      help='Set output dir.')

  options, leftover_args = parser.parse_known_args(argv)
  if '-h' in leftover_args or '--help' in leftover_args:
    print 'The following arguments are specific to toolchain_build_bionic.py:'
    parser.print_help()
    print 'The rest of the arguments are generic, in toolchain_main.py'
    return 1

  pynacl.log_tools.SetupLogging(options.verbose)
  
  if options.clobber:
    Rmdir(options.output)
  Mkdir(options.output)

  if not options.fast_build:
    BuildPNaCl(options)
  UpdatePNaClBuildObjects(options)
  UpdateBionicHeaders(options)
  UpdateBionicIRT(options)

  # Configure Bionic Projects, libc, libm, linker, tests, ...
  ConfigureBionicProjects(options)

  # Build and install IRT header before building GCC
  MakeBionicProject(options, 'libc', ['irt'])
  MakeBionicProject(options, 'libc', ['all'])



if __name__ == '__main__':
  sys.exit(main(sys.argv))
